
#include "Model.h"

#include <QDebug>
#include <QApplication>
#include <QJsonObject>
#include <QJsonDocument>
#include <QFile>
#include <QTimer>

const QString ConfigFileName = "Config.json";
const QString KeyNameOfWebdavCfgList = "WebdavCfgList";
const QString KeyNameOfConnType = "ConnType";
const QString KeyNameOfHostName = "HostName";
const QString KeyNameOfRootPath = "RootPath";
const QString KeyNameOfUserName = "UserName";
const QString KeyNameOfPassword = "Password";
const QString KeyNameOfPort = "Port";
const QString KeyNameOfLocalDirPath = "LocalDirPath";
const QString KeyNameOfRemoteDirPath = "RemoteDirPath";

Model::Model(QObject *parent)
    : QObject(parent)
    , m_workerModelId(1)
{
    // pre init
    qRegisterMetaType<WorkerModel::Error>("WorkerModel::Error");
    qRegisterMetaType<WorkerModel::WebdavCfg>("WorkerModel::WebdavCfg");
    qRegisterMetaType<WorkerModel::State>("WorkerModel::State");

    loadConfig();

    // init worker model
    for (Worker &w : m_workerList) {
        const Worker &wTmp = createWorker(w.Cfg);
        w.Model = wTmp.Model;
        w.Thread = wTmp.Thread;
    }

    // post init
    for (Worker &w : m_workerList) {
        w.Thread->start();
    }
}

Model::~Model()
{
    for (Worker &w : m_workerList) {
        w.Model->deleteLater();
        w.Model = nullptr;

        w.Thread->quit();
        w.Thread->wait();
        w.Thread->deleteLater();
        w.Thread = nullptr;
    }
    m_workerList.clear();
}

const WorkerModel::WebdavCfg &Model::GetWebdavCfg(int workerIndex)
{
    return m_workerList.at(workerIndex).Cfg;
}

int Model::GetWebdavCfgCount()
{
    return m_workerList.size();
}

void Model::AsyncSetWebdavCfg(int workerIndex, const WorkerModel::WebdavCfg &cfg)
{
    const Worker &w = m_workerList.at(workerIndex);
    Q_EMIT ReqThreadSetWebdavCfg(w.Model->GetId(), cfg);
}

void Model::AddWorker(const WorkerModel::WebdavCfg &cfg)
{
    Worker w = createWorker(cfg);
    m_workerList.append(w);

    w.Thread->start();

    saveCfg();
}

void Model::RemoveWorker(int workerIndex)
{
    Worker w = m_workerList.at(workerIndex);
    w.Model->deleteLater();
    w.Model = nullptr;

    w.Thread->quit();
    w.Thread->wait();
    w.Thread->deleteLater();
    w.Thread = nullptr;

    m_workerList.removeAt(workerIndex);

    saveCfg();
}

void Model::onRecvError(WorkerModel::Error err)
{
    QString errMsg = QString("%1。错误代码：%2").arg(err.Msg).arg(err.Type);
    QMessageBox::information(nullptr, "错误", errMsg);
}

void Model::onSetWebdavCfgFinished(bool succeed, const WorkerModel::WebdavCfg &cfg)
{
    if (!succeed) {
        QMessageBox::information(nullptr, "配置修改失败", "失败");
        return;
    }
    WorkerModel *workerModel = dynamic_cast<WorkerModel *>(sender());
    int workerIndex = findWorkerIndexByWorkerModel(workerModel);
    if (workerIndex < 0) {
        qCritical() << Q_FUNC_INFO << "not find worker" << workerModel;
        return;
    }
    Worker &w = m_workerList[workerIndex];

    w.Cfg = cfg;
    saveCfg();
    Q_EMIT SetWebdavCfgFinished(workerIndex, succeed, cfg);

    QMessageBox::information(nullptr, "配置修改完成", "完成");
}

void Model::onWorkerStateChanged(WorkerModel::State s)
{
    WorkerModel *workerModel = dynamic_cast<WorkerModel *>(sender());
    int workerIndex = findWorkerIndexByWorkerModel(workerModel);
    if (workerIndex < 0) {
        qCritical() << Q_FUNC_INFO << "not find worker" << workerModel;
        return;
    }

    Q_EMIT WorkerStateChanged(workerIndex, s);
}

void Model::loadConfig()
{
    const QString &saveDirPath = QStandardPaths::writableLocation(QStandardPaths::StandardLocation::DataLocation);
    QFileInfo fInfo(saveDirPath);
    if (!fInfo.exists()) {
        QDir().mkpath(saveDirPath);
    }
    m_cfgFilePath = QDir::cleanPath(saveDirPath + "/" + ConfigFileName);

    QFile f(m_cfgFilePath);
    if (!f.open(QIODevice::OpenModeFlag::ReadOnly)) {
        qCritical() << Q_FUNC_INFO << f.fileName() << "open failed!";
        return;
    }
    QByteArray ba = f.readAll();
    f.close();

    QJsonParseError err;
    QJsonDocument doc = QJsonDocument::fromJson(ba, &err);
    if (err.error != QJsonParseError::ParseError::NoError) {
        qCritical() << Q_FUNC_INFO << f.fileName() << "parse failed!";
        return;
    }

    QJsonObject jsonObj = doc.object();
    QJsonArray array = jsonObj.value(KeyNameOfWebdavCfgList).toArray();
    for (const QJsonValue &jValue : array) {
        jsonObj = jValue.toObject();

        Worker w;
        w.Cfg.ConnType = QWebdav::QWebdavConnectionType(jsonObj.value(KeyNameOfConnType).toInt());
        w.Cfg.HostName = jsonObj.value(KeyNameOfHostName).toString();
        w.Cfg.RootPath = jsonObj.value(KeyNameOfRootPath).toString();
        w.Cfg.UserName = jsonObj.value(KeyNameOfUserName).toString();
        w.Cfg.Password = jsonObj.value(KeyNameOfPassword).toString();
        w.Cfg.Port = jsonObj.value(KeyNameOfPort).toInt();
        w.Cfg.LocalDirPath = jsonObj.value(KeyNameOfLocalDirPath).toString();
        w.Cfg.RemoteDirPath = jsonObj.value(KeyNameOfRemoteDirPath).toString();

        m_workerList.append(w);
    }
}

void Model::saveCfg()
{
    QJsonArray jsonArray;
    for (const Worker &w : m_workerList) {
        QJsonObject jsonObj;
        jsonObj.insert(KeyNameOfConnType, w.Cfg.ConnType);
        jsonObj.insert(KeyNameOfHostName, w.Cfg.HostName);
        jsonObj.insert(KeyNameOfRootPath, w.Cfg.RootPath);
        jsonObj.insert(KeyNameOfUserName, w.Cfg.UserName);
        jsonObj.insert(KeyNameOfPassword, w.Cfg.Password);
        jsonObj.insert(KeyNameOfPort, w.Cfg.Port);
        jsonObj.insert(KeyNameOfLocalDirPath, w.Cfg.LocalDirPath);
        jsonObj.insert(KeyNameOfRemoteDirPath, w.Cfg.RemoteDirPath);

        jsonArray.append(jsonObj);
    }

    QJsonObject topJsonObj;
    topJsonObj.insert(KeyNameOfWebdavCfgList, jsonArray);

    QJsonDocument doc(topJsonObj);
    doc.toJson();

    QFile f(m_cfgFilePath);
    if (!f.open(QIODevice::OpenModeFlag::WriteOnly)) {
        qCritical() << Q_FUNC_INFO << f.fileName() << "open failed!";
        return;
    }

    f.write(doc.toJson());
    f.close();
}

Worker Model::createWorker(const WorkerModel::WebdavCfg &cfg)
{
    Worker w;
    w.Cfg = cfg;

    QThread *thread = new QThread();
    w.Thread = thread;
    WorkerModel *workerModel = new WorkerModel();
    workerModel->SetId(m_workerModelId);
    workerModel->moveToThread(thread);
    w.Model = workerModel;

    // connection
    // to job
    connect(this, &Model::ReqThreadSetWebdavCfg, workerModel, &WorkerModel::OnReqSetWebdavCfg, Qt::ConnectionType::QueuedConnection);
    // thread to
    connect(thread, &QThread::started, workerModel, [workerModel, w] {
        workerModel->Init(w.Cfg);
    });
    // job to
    connect(workerModel, &WorkerModel::SendError, this, &Model::onRecvError);
    connect(workerModel, &WorkerModel::SetWebdavCfgFinished, this, &Model::onSetWebdavCfgFinished);
    connect(workerModel, &WorkerModel::StateChanged, this, &Model::onWorkerStateChanged);

    m_workerModelId++;

    return w;
}

int Model::findWorkerIndexByWorkerModel(const WorkerModel *model)
{
    for(int i = 0; i < m_workerList.size(); i++) {
        const Worker &w = m_workerList.at(i);
        if (w.Model == model) {
            return i;
        }
    }

    return -1;
}

